package com.example.personalizededucationsystem.service;

import com.example.personalizededucationsystem.dto.request.*;
import com.example.personalizededucationsystem.dto.response.AuthResponse;
import com.example.personalizededucationsystem.dto.response.EmailSendResponse;
import com.example.personalizededucationsystem.dto.response.UpdateProfileResponse;
import com.example.personalizededucationsystem.dto.response.UserListResponse;
import com.example.personalizededucationsystem.model.User;
import com.example.personalizededucationsystem.repository.UserRepository;
import com.example.personalizededucationsystem.security.JwtUtil;
import com.example.personalizededucationsystem.utils.IP_VALUE;
import com.example.personalizededucationsystem.utils.RequestAccountEntry;
import com.example.personalizededucationsystem.utils.VerificationCodeEntry;
import com.example.personalizededucationsystem.utils.VerificationType;
import jakarta.persistence.EntityNotFoundException;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
// REMOVED: @RequiredArgsConstructor
public class AuthService {

    private static final long EMAIL_VALIDITY = 10 * 60 * 1000L; // 10分钟
    private final Map<Pair<String, VerificationType>, Pair<String, VerificationCodeEntry>> verificationCodes = new ConcurrentHashMap<>();
    private final EmailService emailService;
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;
    private final AuthenticationManager authenticationManager;
    Map<String, RequestAccountEntry> checkAccountLink = new ConcurrentHashMap<>();

    @Autowired
    public AuthService(UserRepository userRepository, PasswordEncoder passwordEncoder, JwtUtil jwtUtil, AuthenticationManager authenticationManager, EmailService emailService) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.jwtUtil = jwtUtil;
        this.authenticationManager = authenticationManager;
        this.emailService = emailService;
    }

    public AuthResponse register(RegisterRequest request) {
        User newUser = new User();
        newUser.setUsername(request.getUsername());
        newUser.setEmail(request.getEmail());
        newUser.setPassword(passwordEncoder.encode(request.getPassword()));
        newUser.setRole(request.getRole());
        newUser.setSubscription(User.Subscription.NORMAL); // Default subscription
        newUser.setNickname(request.getNickname().isEmpty() ? request.getUsername() : request.getNickname());
        newUser.setActive(true);

        userRepository.save(newUser);

        String jwtToken = jwtUtil.generateToken(newUser);
        return new AuthResponse(jwtToken);
    }

    public AuthResponse login(LoginRequest request) {
        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            request.getUsername(),
                            request.getPassword()
                    )
            );
        } catch (BadCredentialsException ex) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "用户名或密码错误");
        }

        var user = userRepository.findByUsername(request.getUsername())
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "认证通过却找不到用户"));

        String jwtToken = jwtUtil.generateToken(user);
        return new AuthResponse(jwtToken);
    }

    public boolean CheckIfUserNameExists(UsernameCheckRequest username) {
        System.out.println(username.getUsername());
        return userRepository.findByUsername(username.getUsername()).isPresent();
    }

    public void sendEmailCode(EmailRequest request) {
        String email = request.getEmail();
        cleanExpiredCodes();
        String code = generateUniqueCode();
        VerificationType type = VerificationType.REGISTER;
        VerificationCodeEntry entry = new VerificationCodeEntry(email, type);
        verificationCodes.put(Pair.of(email, type), Pair.of(code, entry));
        emailService.sendVerificationCode(email, type, code);
    }

    public AuthResponse verifyRegistration(RegisterRequest requestVerify) {
        String email = requestVerify.getEmail();
        String code = requestVerify.getCode();
        VerificationType type = VerificationType.REGISTER;

        cleanExpiredCodes();

        Pair<String, VerificationCodeEntry> pair = verificationCodes.get(Pair.of(email, type));

        if (pair != null) System.out.println(pair.getLeft().trim() + " " + requestVerify.getCode());

        if (pair == null || !pair.getLeft().trim().equals(code.trim())) {
            throw new IllegalStateException("Invalid or expired verification code");
        }
        verificationCodes.remove(Pair.of(email, type), pair);
        return register(requestVerify);
    }

    public EmailSendResponse findAccount(EmailRequest request) {
        String email = request.getEmail();
        List<User> users = userRepository.findByEmail(email);
        if (users.isEmpty()) {
            throw new EntityNotFoundException("No user found with this email");
        }
        cleanExpiredResetTokens();
        String token = UUID.randomUUID().toString();
        checkAccountLink.put(token, new RequestAccountEntry(email, System.currentTimeMillis()));
        String resetUrl = IP_VALUE.IP + "/reset-password?token=" + token;

        emailService.sendFindAccountLink(email, resetUrl);
        return new EmailSendResponse("Reset password link sent to email.");
    }

    public UserListResponse getUsernamesByToken(AccountTokenLinkRequest request) {
        RequestAccountEntry entry = checkAccountLink.get(request.getToken());
        if (entry == null) {
            throw new IllegalArgumentException("Invalid or expired reset link");
        }
        cleanExpiredResetTokens();
        List<User> users = userRepository.findByEmail(entry.email());
        if (users.isEmpty()) {
            throw new EntityNotFoundException("No user found with this email");
        }

        return new UserListResponse(users.stream().map(User::getUsername).toList());
    }

    public UpdateProfileResponse resetPasswordByTokenAndUsername(ResetPasswordByTokenRequest request) {
        RequestAccountEntry entry = checkAccountLink.get(request.getToken());
        if (entry == null) {
            throw new IllegalArgumentException("Invalid or expired reset link.");
        }

        if (!request.getNewPassword().equals(request.getConfirmPassword())) {
            throw new IllegalArgumentException("Passwords do not match.");
        }
        cleanExpiredResetTokens();
        User user = userRepository.findByUsername(request.getUsername()).orElseThrow(() -> new EntityNotFoundException("User not found"));
        if (!user.getEmail().equals(entry.email())) {
            throw new IllegalArgumentException("Username does not belong to the email associated with this reset link");
        }
        user.setPassword(passwordEncoder.encode(request.getNewPassword()));
        userRepository.save(user);
        checkAccountLink.remove(request.getToken());

        return new UpdateProfileResponse("Password reset successfully.");
    }

    private void cleanExpiredCodes() {
        verificationCodes.entrySet().removeIf(entry -> entry.getValue().getRight().isExpired(EMAIL_VALIDITY));
    }

    private String generateUniqueCode() {
        String code;
        int attempts = 0;
        do {
            code = RandomStringUtils.randomNumeric(6);
            attempts++;
        } while (isCodeDuplicate(code) && attempts < 10);

        if (attempts >= 10) {
            throw new IllegalStateException("Failed to generate unique verification code after 10 attempts.");
        }

        return code;
    }

    private boolean isCodeDuplicate(String code) {
        return verificationCodes.values().stream().anyMatch(pair -> pair.getLeft().equals(code));
    }

    private void cleanExpiredResetTokens() {
        long now = System.currentTimeMillis();
        checkAccountLink.entrySet().removeIf(entry -> now - entry.getValue().requestTime() > EMAIL_VALIDITY);
    }


}